﻿#region Using directives

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using Weazel.Gezel.Model.Errors;

#endregion

namespace Weazel.Badger
{
  class Program
  {
    private static bool useNonDefaultDestination;
    private static List<string> inputFiles = new List<string>();

    /// <summary>
    /// Convert the model 
    /// </summary>
    /// <returns>true iff successful</returns>
    static bool convertModel(Configuration configuration)
    {
      // read the input file
      string filename = inputFiles[0];

      StreamReader reader = null;
      string description = null;
      
      if(configuration.Verbose)
        Console.Write("{0,-50:G}", "reading files"); 

      try
      {
        reader = new StreamReader(filename);
        description = reader.ReadToEnd();
      }
      catch (Exception)
      {
        if (configuration.Verbose)
        {
          Console.ForegroundColor = ConsoleColor.Red;
          Console.WriteLine("failed (Error reading file '" + filename + "')");
          Console.ResetColor();
        }

        return false;
      }
      finally
      {
        if (reader != null)
          reader.Close();
      }

      if (configuration.Verbose)
      {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("ok");
        Console.ResetColor();
      }

      // now, carry out the conversion ..
      Converter converter = new Converter(filename, description, configuration);
      
      // get the result
      ConversionResult result = converter.Convert();

      if (result.Warnings.Count > 0)
      {
        foreach (ConversionWarning warning in result.Warnings)
          Console.WriteLine("Warning: " + warning.Message);
      }

      // print some feedback to the user
      if (result.NumberOfErrors > 0)
      {     
        // write parse errors
        if (result.ParseErrors.Count > 0)
        {
          Console.WriteLine("Errors encountered during parsing: ");
          Console.WriteLine();

          foreach (ParseError error in result.ParseErrors)
            Console.WriteLine(error);
        }

        // write verification errors
        if (result.VerificationErrors.Count > 0)
        {
          Console.WriteLine("Errors encountered during model verification:");
          Console.WriteLine();

          foreach (VerificationError error in result.VerificationErrors)
            Console.WriteLine(error);
        }

        // write conversion errors
        if (result.ConversionErrors.Count > 0)
        {
          Console.WriteLine("Errors encountered during model conversion:");
          Console.WriteLine();

          foreach (ConversionError error in result.ConversionErrors)
            Console.WriteLine(error);
        }        
      }
      else
      {
        Console.WriteLine();
        Console.WriteLine("Files written: ");
        foreach (string fileWritten in result.FilesWritten)
          Console.WriteLine("\t " + fileWritten);
        Console.WriteLine();

        Console.WriteLine("Conversion succeeded (errors: {0}, warnings: {0})", result.NumberOfErrors, result.NumberOfWarnings);
      }     

      return true;
    }

    /// <summary>
    /// Check the validity of a user specified clock or reset name
    /// </summary>
    /// <param name="name">name to check</param>
    /// <returns>true iff ok</returns>
    static bool isValidClockOrResetName(string name)
    {
      bool invalid =
        name.Length == 0      ||
        name[0] == '-'        ||
        Char.IsDigit(name[0]) ||
        name.Contains(" ");

      // todo: check for VHDL keywords?

      return !invalid;
    }

    static Configuration processCommandLineArguments(string[] args)
    {
      Configuration configuration = new Configuration();

      if (args.Length == 0)
        return null;

      int index = 0;

      while (index < args.Length)
      {
        // check for verbose flag
        if (args[index].ToLower() == "-v")
        {
          configuration.Verbose = true;
        }
        // check for clock name flag
        else if (args[index].ToLower() == "-c")
        {
          if (index + 1 < args.Length)
          {          
            configuration.ClockName = args[++index];

            if (!isValidClockOrResetName(configuration.ClockName))
            {
              Console.WriteLine("Failed: invalid clock name '{0}'", configuration.ClockName);
              return null;
            }
          }
          else
          {
            Console.WriteLine("Failed: '-c' option must be followed by a value clock name");
            return null;
          }
        }
        // check for reset name flag
        else if (args[index].ToLower() == "-r")
        {
          if (index + 1 < args.Length)
          {
            configuration.ResetName = args[++index];
            if (!isValidClockOrResetName(configuration.ClockName))
            {
              Console.WriteLine("Failed: invalid clock name '{0}'", configuration.ClockName);
              return null;
            }
          }
          else
          {
            Console.WriteLine("Failed: '-r' option must be followed by a value reset name");
            return null;
          }

          index++;
        }
        // check for destination flag
        else if (args[index].ToLower() == "-d")
        {
          // If already set, abort with an error message
          if (useNonDefaultDestination)
          {
            Console.WriteLine("Failed: output destination directory already set");
            return null;
          }

          // Must be followed by a valid directory name
          if (index + 1 < args.Length)
          {
            configuration.OutputFolder = args[++index];

            try
            {
              if (!System.IO.Directory.Exists(configuration.OutputFolder))
                System.IO.Directory.CreateDirectory(configuration.OutputFolder);
            }
            catch (ArgumentException)
            {
              Console.WriteLine("Failed: '" + configuration.OutputFolder + "' is not a valid directory");
              return null;
            }
          }
          else
          {
            Console.WriteLine("Failed: missing destination directory");
            return null;
          }

          useNonDefaultDestination = true;
        }
        else if (System.IO.File.Exists(args[index]))
        {
          inputFiles.Add(args[index]);
        }
        else
        {
          Console.WriteLine("Unknown argument: {0}", args[index]);
          return null;
        }
        
        index++;
      }

      if (inputFiles.Count == 0)
      {
        Console.WriteLine("Failed: no input files specified");
        return null;
      }

      return configuration;
    }

    /// <summary>
    /// prints a message about how to use this program to the 
    /// console.
    /// </summary>
    static void printUsage()
    {
      Console.WriteLine("Usage:");
      Console.WriteLine("badger.exe [-v] [-c clock] [-r reset] [-d destination] input");
      Console.WriteLine("\t -v verbose");
      Console.WriteLine("\t -c \"clock name\" set the name of the clock signal");
      Console.WriteLine("\t -r \"reset name\" set the name of the reset signal");
      Console.WriteLine("\t -d \"directory\" write output to specific destination");
    }

    static void Main(string[] args)
    {
      Console.WriteLine("Badger Gezel-to-vhdl conversion utillity");
      Console.WriteLine();

      Configuration configuration = processCommandLineArguments(args);

      if (configuration == null)
        printUsage();
      else
        convertModel(configuration);
    }
  }
}
